package modelo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.DebugGL;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.swing.JColorChooser;
import javax.swing.JOptionPane;

import controle.Transformacao;

/**
 * Faz todo o controle na tela de eventos de mouse e teclado<br>
 * Responsável por Coordenar o desenho da tela
 */
public class Mundo
		implements
			GLEventListener,
			KeyListener,
			MouseListener,
			MouseMotionListener {

	private GL gl;
	private GLU glu;
	private GLAutoDrawable glDrawable;
	private double maxX = 0;
	private double maxY = 0;
	private double minX = 0;
	private double minY = 0;
	private double xInicioRastro1;
	private double yInicioRastro1;
	private double xInicioRastro2;
	private double yInicioRastro2;
	private double xFimRastro;
	private double yFimRastro;
	private static double DELTA_ZOOM = 5;
	private List<ObjetoGrafico> objetosGraficos;
	/**
	 * 1 - Polígono<br>
	 * 2 - Círculo<br>
	 * 3 - Spline<br>
	 */
	private int objetoSelecionado = 1;
	private boolean modoSelecao = false;
	private double metadeX = 0;
	private double metadeY = 0;
	private ObjetoGrafico objetoGraficoAtual = null;
	private Poligono poligonoPontoSelecionado;
	private Ponto pontoPoligonoSelecionado;
	private Color corSelecionadaAtual = Color.BLUE;
	private boolean poligonoAbertoAtual = false;
	private Transformacao matrixObjetoSelecionado = new Transformacao();
	private Transformacao matrixMundo = new Transformacao();
	/**
	 * @param dimensao
	 */
	public Mundo(Dimension dimensao) {
		objetosGraficos = new ArrayList<ObjetoGrafico>();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void display(GLAutoDrawable arg0) {
		System.out.println(" --- display ---");

		gl.glClear(GL.GL_COLOR_BUFFER_BIT);
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();

		// configurar window
		/**
		 * gluOrtho2D (0.0f, 250.0f*w/h, 0.0f, 250.0f); O prot�tipo desta fun��o
		 * �: void gluOrtho2D(GLdouble left, GLdouble right, GLdouble
		 * bottom,GLdouble top);.<br>
		 * No sistema de coordenadas cartesianas, os valores left e right
		 * especificam os limites m�nimo e m�ximo no eixo X;<br>
		 * analogamente, bottom e top especificam os limites m�nimo e m�ximo no
		 * eixo Y.
		 */
		glu.gluOrtho2D(minX, maxX, minY, maxY);

		gl.glPushMatrix();
		gl.glMultMatrixd(matrixMundo.getDate(), 0);
		for (ObjetoGrafico obj : objetosGraficos) {
			obj.desenhar(gl);
		}
		gl.glPopMatrix();
		displaySelecionado();
		if (!modoSelecao && objetoSelecionado == 1) {
			if (objetoGraficoAtual != null) {
				gl.glPushMatrix();
				gl.glMultMatrixd(objetoGraficoAtual.getTransformacao()
						.getDate(), 0);
				gl.glBegin(GL.GL_LINE_STRIP);
				gl.glColor3f(0, 0, 0);

				if (!((Poligono) objetoGraficoAtual).isAberto()) {
					Ponto t = new Ponto(xInicioRastro1, yInicioRastro1, 0, 0);
					t = matrixMundo.transformPonto(t);
					gl.glVertex2d(t.getX(), t.getY());
				}
				Ponto p = new Ponto(xFimRastro, yFimRastro, 0, 0);
				p = matrixMundo.transformPonto(p);
				gl.glVertex2d(p.getX(), p.getY());
				p = new Ponto(xInicioRastro2, yInicioRastro2, 0, 0);
				p = matrixMundo.transformPonto(p);
				gl.glVertex2d(p.getX(), p.getY());

				gl.glEnd();
				gl.glPopMatrix();
			}
		}

		gl.glFlush();
	}

	private void displaySelecionado() {
		if (objetoGraficoAtual == null || !modoSelecao)
			return;
		gl.glPushMatrix();
		gl.glMultMatrixd(objetoGraficoAtual.getTransformacao().getDate(), 0);
		gl.glBegin(GL.GL_LINE_LOOP);
		gl.glColor3d(objetoGraficoAtual.getCor().getRed(), objetoGraficoAtual
				.getCor().getGreen(), objetoGraficoAtual.getCor().getBlue());
		for (Ponto p : objetoGraficoAtual.getbBox().getPontos()) {
			/**
			 * Converte o ponto do objeto para o mundo e desenha
			 */
			Ponto t = matrixMundo.transformPonto(p);
			gl.glVertex2d(t.getX(), t.getY());
		}
		gl.glEnd();
		gl.glPopMatrix();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {
		System.out.println(" --- displayChanged ---");

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void init(GLAutoDrawable drawable) {
		System.out.println(" --- init ---");
		glDrawable = drawable;
		gl = drawable.getGL();
		glu = new GLU();
		glDrawable.setGL(new DebugGL(gl));

		int height = glDrawable.getHeight();
		int metadeHeight = height / 2;
		metadeY = metadeHeight;

		int width = glDrawable.getWidth();
		int metadeWidth = width / 2;
		metadeX = metadeWidth;

		maxX = metadeWidth;
		minX = metadeWidth * (-1);

		maxY = metadeHeight;
		minY = metadeHeight * (-1);

		// fundo com cinza claro
		gl.glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3,
			int arg4) {
		System.out.println(" --- reshape ---");

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyTyped(KeyEvent e) {

	}

	/**
	 * Retorna um texto formatado com as opções
	 * 
	 * @return um texto formatado com as opções
	 */
	public String getOpcoes() {
		return "Opções:\n\n"
				+ "'1' - Desenhar Polígono\n"
				+ "'2' - Desenhar Círculo\n"
				+ "'3' - Desenhar Spline\n"
				+ "'4' - Alterar Modo de iteração(Seleção/Desenho)\n"
				+ "'5' - Alterar Cor Do objeto para Desenhar\n"
				+ "'6' - Alterar tipo do polígono(Aberto ou fechado)\n"
				+ "DELETE (Em modo seleção)- Remover Objeto Selecionado\n"
				+ "'R' - (Em modo seleção) - Rotacionado Polígono Selecionado\n"
				+ "'T' - (Em modo seleção) - Translada Polígono Selecionado\n"
				+ "'S' - (Em modo seleção) - Scala Polígono Selecionado\n"
				+ "'+' - Zoom In\n" + "'-' - Zoom Out\n";
	}
	/**
	 * Processa uma tecla pressionada
	 * 
	 * @param keyCode
	 *            O - número pressionado<br>
	 *            1 - Desenhar Polígono<br>
	 *            2 - Desenhar Círculo<br>
	 *            3 - Desenhar Spline<br>
	 *            4 - Alterar Modo de iteração(Seleção/Desenho)<br>
	 *            5 - Alterar Cor Do objeto para Desenhar<br>
	 *            6 - Criar novo Polígono e Alterar tipo do polígono(Aberto ou
	 *            fechado)<br>
	 *            KeyEvent.VK_DELETE(127) - Remover Objeto
	 */
	private void processOptions(int keyCode) {
		Transformacao matrixTranslate = null;
		Transformacao matrixScala = null;
		Transformacao matrixRotate = null;
		Transformacao matrixGlobal = null;
		Transformacao matrixTranslateInverse = null;
		switch (keyCode) {
			case KeyEvent.VK_1 :
			case KeyEvent.VK_2 :
			case KeyEvent.VK_3 :
				selecionouDesenharObjetoGrafico(keyCode);
				break;
			case KeyEvent.VK_4 :
				selecionouModoSelecao();
				break;
			case KeyEvent.VK_5 :
				informarCor();
				break;
			case KeyEvent.VK_6 :
				informarTipoPoligono();
				break;
			case KeyEvent.VK_DELETE :
				pressionouDelete();
				break;
			case KeyEvent.VK_EQUALS :
				zoomMais();
				break;
			case KeyEvent.VK_MINUS :
				zoomMenos();
				break;
			case KeyEvent.VK_LEFT :
				pressionouEsquerda();
				break;
			case KeyEvent.VK_UP :
				pressionouCima();
				break;
			case KeyEvent.VK_RIGHT :
				pressionouDireita();
				break;
			case KeyEvent.VK_DOWN :
				pressionouBaixo();
				break;
			case KeyEvent.VK_T :
				if (objetoGraficoAtual != null && modoSelecao) {
					matrixTranslate = new Transformacao();
					matrixGlobal = new Transformacao();
					matrixTranslate.makeTranslation(new Ponto(1, 1, 0, 0));
					matrixObjetoSelecionado = objetoGraficoAtual
							.getTransformacao()
							.transformMatrix(matrixTranslate);
					objetoGraficoAtual
							.setTransformacao(matrixObjetoSelecionado);
				} else {
					JOptionPane.showMessageDialog(null,
							"Nenhum Objeto Selecionado!");
				}
				break;
			case KeyEvent.VK_R :
				if (objetoGraficoAtual != null && modoSelecao) {
					matrixRotate = new Transformacao();
					matrixGlobal = new Transformacao();
					matrixTranslate = new Transformacao();
					matrixTranslateInverse = new Transformacao();
					double Lx = -1
							* (objetoGraficoAtual.getbBox().getCenterPonto()
									.getX());
					double Ly = -1
							* (objetoGraficoAtual.getbBox().getCenterPonto()
									.getY());
					Ponto p = new Ponto(Lx, Ly, 0, 0);
					matrixTranslate.makeTranslation(p);

					matrixRotate
							.makeZRotation(Transformacao.RAS_DEG_TO_RAD * 10);

					Lx = p.getX() * -1;
					Ly = p.getY() * -1;
					p.setX(Lx);
					p.setY(Ly);
					matrixTranslateInverse.makeTranslation(p);

					matrixGlobal = matrixGlobal
							.transformMatrix(matrixTranslateInverse);
					matrixGlobal = matrixGlobal.transformMatrix(matrixRotate);
					matrixGlobal = matrixGlobal
							.transformMatrix(matrixTranslate);

					matrixObjetoSelecionado = objetoGraficoAtual
							.getTransformacao().transformMatrix(matrixGlobal);
					objetoGraficoAtual
							.setTransformacao(matrixObjetoSelecionado);
				} else {
					JOptionPane.showMessageDialog(null,
							"Nenhum Objeto Selecionado!");
				}
				break;
			case KeyEvent.VK_S :
				if (objetoGraficoAtual != null && modoSelecao) {
					matrixScala = new Transformacao();
					matrixGlobal = new Transformacao();
					matrixGlobal = new Transformacao();
					matrixTranslate = new Transformacao();
					matrixTranslateInverse = new Transformacao();
					double Lx = -1
							* (objetoGraficoAtual.getbBox().getCenterPonto()
									.getX());
					double Ly = -1
							* (objetoGraficoAtual.getbBox().getCenterPonto()
									.getY());
					Ponto p = new Ponto(Lx, Ly, 0, 0);
					matrixTranslate.makeTranslation(p);

					matrixScala.makeScale(1.01, 1.01, 0);

					Lx = p.getX() * -1;
					Ly = p.getY() * -1;
					p.setX(Lx);
					p.setY(Ly);
					matrixTranslateInverse.makeTranslation(p);

					matrixGlobal = matrixGlobal
							.transformMatrix(matrixTranslateInverse);
					matrixGlobal = matrixGlobal.transformMatrix(matrixScala);
					matrixGlobal = matrixGlobal
							.transformMatrix(matrixTranslate);

					matrixObjetoSelecionado = objetoGraficoAtual
							.getTransformacao().transformMatrix(matrixGlobal);
					objetoGraficoAtual
							.setTransformacao(matrixObjetoSelecionado);
				} else {
					JOptionPane.showMessageDialog(null,
							"Nenhum Objeto Selecionado!");
				}
				break;
		}
	}
	private void pressionouBaixo() {
		if (pontoPoligonoSelecionado != null)
			pontoPoligonoSelecionado.setY(pontoPoligonoSelecionado.getY() - 1);
	}

	private void pressionouDireita() {
		if (pontoPoligonoSelecionado != null)
			pontoPoligonoSelecionado.setX(pontoPoligonoSelecionado.getX() + 1);
	}

	private void pressionouCima() {
		if (pontoPoligonoSelecionado != null)
			pontoPoligonoSelecionado.setY(pontoPoligonoSelecionado.getY() + 1);
	}

	private void pressionouEsquerda() {
		if (pontoPoligonoSelecionado != null)
			pontoPoligonoSelecionado.setX(pontoPoligonoSelecionado.getX() - 1);
	}

	private void pressionouDelete() {
		if (this.modoSelecao
				&& (this.objetoGraficoAtual != null || this.pontoPoligonoSelecionado != null)) {
			if (this.pontoPoligonoSelecionado != null) {
				this.poligonoPontoSelecionado
						.removePonto(pontoPoligonoSelecionado);
				poligonoPontoSelecionado.getbBox().iniciaValores();
				this.pontoPoligonoSelecionado = null;
				this.poligonoPontoSelecionado = null;
			} else {
				objetosGraficos.remove(objetoGraficoAtual);
				objetoGraficoAtual = null;
			}
		} else {
			JOptionPane.showMessageDialog(null, "Nenhum Objeto Selecionado!");
		}
	}

	private void selecionouModoSelecao() {
		modoSelecao = true;
	}

	private void selecionouDesenharObjetoGrafico(int keyCode) {
		objetoSelecionado = keyCode - 48;
		modoSelecao = false;
		objetoGraficoAtual = null;
		xFimRastro = 0;
		yFimRastro = 0;
		xInicioRastro1 = 0;
		xInicioRastro2 = 0;
		yInicioRastro1 = 0;
		yInicioRastro2 = 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyPressed(KeyEvent e) {
		System.out.println(" --- keyPressed ---" + e.getKeyCode());
		int keyCode = e.getKeyCode();
		processOptions(keyCode);
		glDrawable.repaint();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void keyReleased(KeyEvent e) {

	}

	private void zoomMais() {
		maxX -= DELTA_ZOOM;
		minX += DELTA_ZOOM;
		maxY -= DELTA_ZOOM;
		minY += DELTA_ZOOM;
		if (maxX == minX || maxY == minY) {
			System.err.println("Revert p/ zmais");
			zoomMenos();
		}

	}

	private void zoomMenos() {
		maxX += DELTA_ZOOM;
		minX -= DELTA_ZOOM;
		maxY += DELTA_ZOOM;
		minY -= DELTA_ZOOM;
		if (maxX == minX || maxY == minY) {
			System.err.println("Revert p/ zmenos");
			zoomMais();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseClicked(MouseEvent e) {
		System.out.println(e.getX());
		System.out.println(e.getY());

		double x = e.getX() - metadeX;
		double y = e.getY() - metadeY;

		x = ndc(x);
		y = ndc(y);
		y = y * -1;
		Ponto p = new Ponto(x, y, 0, 0);
		p = matrixMundo.transformPonto(p);
		// clicar 4 para entrar em modo de edição
		// com isso ela desabilita a criação de novos componentes
		// volta para a criação se clicar em 1, 2, ou 3
		if (modoSelecao) {
			objetoGraficoAtual = null;
			pontoPoligonoSelecionado = null;
			poligonoPontoSelecionado = null;
			for (ObjetoGrafico obj : objetosGraficos) {
				// primeiro verifica se selecionou um dos vértices de algum
				// polígono
				if (obj.getClass() == Poligono.class) {
					Poligono poligono = (Poligono) obj;
					for (Ponto ponto : poligono.getPontos()) {
						int margem = 3;
						if (p.getX() >= ponto.getX() - margem
								&& p.getX() <= ponto.getX() + margem
								&& p.getY() >= ponto.getY() - margem
								&& p.getY() <= ponto.getY() + margem) {
							this.pontoPoligonoSelecionado = ponto;
							this.poligonoPontoSelecionado = poligono;
							this.poligonoPontoSelecionado
									.setPontoSelecionado(this.pontoPoligonoSelecionado);
							break;
						}
					}
				}

				// se não selecionou um vértice de algum polígono, verifica a
				// bbox
				if (this.pontoPoligonoSelecionado == null) {
					if (obj.getbBox().estaDentroDaBBox(p)) {
						System.out.println("Dentro da BBOx");
						if (obj.getClass() == Poligono.class) {
							Poligono poligono = (Poligono) obj;
							int qtde = 0;
							for (int i = 0; i < poligono.getPontos().size(); i++) {
								Ponto p1 = poligono.getPontos().get(i);
								Ponto p2 = null;
								int proximo = i + 1;
								if (proximo == poligono.getPontos().size()) {
									p2 = poligono.getPontos().get(0);
								} else {
									p2 = poligono.getPontos().get(proximo);
								}
								p1 = matrixMundo.transformPonto(p1);
								p2 = matrixMundo.transformPonto(p2);
								double ti = (p.getY() - p1.getY())
										/ (p2.getY() - p1.getY());
								if (ti >= 0 && ti <= 1) {
									double xi = p1.getX()
											+ (p2.getX() - p1.getX()) * ti;
									if (xi > p.getX()) {
										qtde++;
									}
								}
							}

							System.out.println(qtde);
							if (qtde % 2 == 1) {
								objetoGraficoAtual = obj;
							}

						} else {
							objetoGraficoAtual = obj;
						}
					}
				}
			}
		} else {
			switch (objetoSelecionado) {
				case 1 : // poligono
					// TODO
					if (objetoGraficoAtual == null) {
						objetoGraficoAtual = new Poligono(poligonoAbertoAtual,
								corSelecionadaAtual);
						((Poligono) objetoGraficoAtual).addPonto(new Ponto(x, y, 0, 0));
						objetosGraficos.add(objetoGraficoAtual);
					} else {
						((Poligono) objetoGraficoAtual).addPonto(p);
					}
					break;
				case 2 : // circulo
					if (objetoGraficoAtual == null) {
						Circulo circulo = new Circulo(p, 10,
								corSelecionadaAtual);
						objetosGraficos.add(circulo);
					}
					break;
				case 3 : // spline
					if (objetoGraficoAtual == null) {
						Spline s = new Spline(corSelecionadaAtual);
						s.addPonto(p);
						objetosGraficos.add(s);
						objetoGraficoAtual = s;
					} else {
						Spline s = (Spline) objetoGraficoAtual;
						s.addPonto(p);
						if (s.getPontos().size() == 4) {
							objetoGraficoAtual = null;
						}
					}
					break;
			}
		}

		glDrawable.repaint();
	}

	private double ndc(double e) {
		double d = ((glDrawable.getWidth() / 2) * -1);
		double f = (glDrawable.getWidth() / 2);
		double deltaO = maxX - minX;
		double deltaD = f - d;

		double b = ((e - d) * (deltaO / deltaD)) + minX;
		return b;
	}

	private void informarCor() {
		Color cor = JColorChooser.showDialog(null, "Selecione a cor",
				corSelecionadaAtual);

		if (cor != null) {
			if (objetoGraficoAtual != null) {
				objetoGraficoAtual.setCor(cor);
			}
			corSelecionadaAtual = cor;
		}
	}

	private void informarTipoPoligono() {
		if (objetoSelecionado != 1) {
			JOptionPane
					.showMessageDialog(null,
							"Você não selecionou para Desenhar Polígono.(Selecione a Opção 1)");
			return;
		}
		int option = JOptionPane.showConfirmDialog(null, "Polígono Aberto?",
				"Tipo do novo Polígono", JOptionPane.YES_NO_CANCEL_OPTION);
		poligonoAbertoAtual = false;
		if (option == JOptionPane.YES_OPTION) {
			poligonoAbertoAtual = true;
		}
		if (option == JOptionPane.YES_OPTION || option == JOptionPane.NO_OPTION) {
			objetoGraficoAtual = null;
		}

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mousePressed(MouseEvent e) {

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseReleased(MouseEvent e) {

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseExited(MouseEvent e) {
		System.out.println("" + minX + " " + maxX);
		System.out.println("" + e.getX() + " " + e.getY());
		double x = e.getX() - metadeX;
		double y = e.getY() - metadeY;
		Transformacao t = new Transformacao();
		if (x > maxX) {
			maxX += DELTA_ZOOM;
			minX += DELTA_ZOOM;
			t.makeTranslation(new Ponto(DELTA_ZOOM, 0, 0, 0));
			matrixMundo.transformMatrix(t);
			System.out.println("maxX");
		} else if (x < minX) {
			maxX -= DELTA_ZOOM;
			minX -= DELTA_ZOOM;
			t.makeTranslation(new Ponto(-DELTA_ZOOM, 0, 0, 0));
			matrixMundo.transformMatrix(t);
			System.out.println("minX");
		} else if (y > maxY) {
			maxY -= DELTA_ZOOM;
			minY -= DELTA_ZOOM;
			t.makeTranslation(new Ponto(0, -DELTA_ZOOM, 0, 0));
			matrixMundo.transformMatrix(t);
			System.out.println("maxY");
		} else if (y < minY) {
			maxY += DELTA_ZOOM;
			minY += DELTA_ZOOM;
			t.makeTranslation(new Ponto(0, DELTA_ZOOM, 0, 0));
			matrixMundo.transformMatrix(t);
			System.out.println("minY");
		}
		glDrawable.repaint();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseDragged(MouseEvent e) {
		// TODO Auto-generated method stub
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void mouseMoved(MouseEvent e) {
		if (!modoSelecao && objetoSelecionado == 1) {
			if (objetoGraficoAtual != null) {
				Poligono poligono = (Poligono) objetoGraficoAtual;
				if (!poligono.getPontos().isEmpty()) {
					double x = e.getX() - metadeX;
					double y = e.getY() - metadeY;

					// x = ndc(x);
					// y = ndc(y);
					y = y * -1;
					Ponto ponto1 = poligono.getPontos().get(0);

					Ponto ponto2 = poligono.getPontos().get(
							poligono.getPontos().size() - 1);
					xInicioRastro1 = ponto1.getX();
					yInicioRastro1 = ponto1.getY();
					xInicioRastro2 = ponto2.getX();
					yInicioRastro2 = ponto2.getY();
					xFimRastro = x;
					yFimRastro = y;
					glDrawable.repaint();
				}
			}
		}
	}

	/**
	 * Limpa a tela
	 */
	public void clear() {
		objetosGraficos.clear();
		objetoSelecionado = 1;
		objetoGraficoAtual = null;
		glDrawable.repaint();
	}
}
